/**
 * 
 */
package com.tiduyun.data.exchange.util.http.config;

import java.net.InetSocketAddress;
import java.net.Proxy;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.SSLContext;

import org.apache.http.HttpHost;
import org.apache.http.client.CookieStore;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.DnsResolver;
import org.apache.http.conn.HttpConnectionFactory;
import org.apache.http.conn.ManagedHttpClientConnection;
import org.apache.http.conn.SchemePortResolver;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.ssl.SSLContexts;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.tiduyun.data.exchange.util.http.EmptyCookieStore;
import com.tiduyun.data.exchange.util.http.SocksProxyConnectionSocketFactory;
import com.tiduyun.data.exchange.util.http.SocksProxySSLConnectionSocketFactory;

/**
 * @author wuzheng@tiduyun.com
 *
 */
@Configuration
@EnableConfigurationProperties
@ConditionalOnProperty("cmp.http-client.enable")
public class CmpHttpClientBuilderAutoConfiguration {

    public static final String BEAN_HTTP_CLIENT_BUILDER = "cmpHttpClientBuilder";

    public static final String BEAN_COOKIE_STORE = "cmpHttpClientCookieStore";

    public static final String BEAN_PROXY = "cmpHttpClientProxy";

    public static final String BEAN_SSL_CONTEXT = "cmpHttpClientSslContext";

    public static final String BEAN_HTTP_CONTEXT = "cmpHttpClientHttpContext";

    @Autowired
    private CmpHttpClientBuilderProperties httpClientBuilderProperties;

    @ConditionalOnProperty("cmp.http-client.cookieStore")
    @ConditionalOnMissingBean(value = { CookieStore.class }, name = { BEAN_COOKIE_STORE })
    @Bean(BEAN_COOKIE_STORE)
    public CookieStore cmpHttpClientCookieStore() {
        return cmpHttpClientCookieStore(httpClientBuilderProperties.getCookieStore());
    }

    private CookieStore cmpHttpClientCookieStore(String cookieStoreType) {
        if ("empty".equalsIgnoreCase(cookieStoreType)) {
            return new EmptyCookieStore();
        } else if ("basic".equalsIgnoreCase(cookieStoreType)) {
            return new BasicCookieStore();
        }
        throw new IllegalArgumentException("Unsupport cookie store: " + cookieStoreType);
    }

    @ConditionalOnProperty("cmp.http-client.proxy")
    @ConditionalOnMissingBean(value = { Proxy.class }, name = { BEAN_PROXY })
    @Bean(BEAN_PROXY)
    public Proxy cmpHttpClientProxy() {
        CmpHttpClientProxyProperties proxyProps = httpClientBuilderProperties.getProxy();
        String proxyType = proxyProps.getType();

        Proxy.Type proxyTypeValue = null;
        if ("socks".equalsIgnoreCase(proxyType)) {
            proxyTypeValue = Proxy.Type.SOCKS;
        } else {
            proxyTypeValue = Proxy.Type.HTTP;
        }

        return new Proxy(proxyTypeValue, new InetSocketAddress(proxyProps.getHost(), proxyProps.getPort()));
    }

    @ConditionalOnMissingBean(value = { HttpClientContext.class }, name = { BEAN_HTTP_CONTEXT })
    @ConditionalOnBean(value = { Proxy.class }, name = { BEAN_PROXY })
    @Bean(BEAN_HTTP_CONTEXT)
    public HttpClientContext cmpHttpClientHttpClientContext(@Qualifier(BEAN_PROXY) Proxy proxy) {
        HttpClientContext httpClientContext = HttpClientContext.create();
        httpClientContext.setAttribute("proxy", proxy);
        return httpClientContext;
    }

    @Bean(BEAN_HTTP_CLIENT_BUILDER)
    public HttpClientBuilder cmpHttpClientBuilder(
            @Autowired(required = false) @Qualifier(BEAN_COOKIE_STORE) CookieStore cookieStore,
            @Autowired(required = false) @Qualifier(BEAN_SSL_CONTEXT) SSLContext sslContext,
            @Autowired(required = false) @Qualifier(BEAN_PROXY) Proxy proxy) {
        HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();

        // connection manager config
        if (httpClientBuilderProperties.getMaxConnPerRoute() != null) {
            httpClientBuilder.setMaxConnPerRoute(httpClientBuilderProperties.getMaxConnPerRoute());
        }
        if (httpClientBuilderProperties.getMaxConnTotal() != null) {
            httpClientBuilder.setMaxConnTotal(httpClientBuilderProperties.getMaxConnTotal());
        }
        if (httpClientBuilderProperties.getConnTimeToLive() != null) {
            httpClientBuilder.setConnectionTimeToLive(httpClientBuilderProperties.getConnTimeToLive(), TimeUnit.MILLISECONDS);
        }

        // default http config
        if (sslContext != null) {
            httpClientBuilder.setSSLContext(sslContext);
        }
        if (httpClientBuilderProperties.getUserAgent() != null) {
            httpClientBuilder.setUserAgent(httpClientBuilderProperties.getUserAgent());
        }
        if (cookieStore != null) {
            httpClientBuilder.setDefaultCookieStore(cookieStore);
        }

        // default socket config
        CmpHttpClientSocketConfigProperties socketConfigProps = httpClientBuilderProperties.getSocket();
        SocketConfig customSocketConfig = null;
        if (socketConfigProps != null) {
            SocketConfig.Builder socketConfigBuilder = SocketConfig.custom();
            if (socketConfigProps.getSoTimeout() != null) {
                socketConfigBuilder.setSoTimeout(socketConfigProps.getSoTimeout());
            }
            if (socketConfigProps.getSoReuseAddress() != null) {
                socketConfigBuilder.setSoReuseAddress(socketConfigProps.getSoReuseAddress());
            }
            if (socketConfigProps.getSoLinger() != null) {
                socketConfigBuilder.setSoLinger(socketConfigProps.getSoLinger());
            }
            if (socketConfigProps.getSoKeepAlive() != null) {
                socketConfigBuilder.setSoKeepAlive(socketConfigProps.getSoKeepAlive());
            }
            if (socketConfigProps.getTcpNoDelay() != null) {
                socketConfigBuilder.setTcpNoDelay(socketConfigProps.getTcpNoDelay());
            }
            if (socketConfigProps.getSndBufSize() != null) {
                socketConfigBuilder.setSndBufSize(socketConfigProps.getSndBufSize());
            }
            if (socketConfigProps.getRcvBufSize() != null) {
                socketConfigBuilder.setRcvBufSize(socketConfigProps.getRcvBufSize());
            }
            if (socketConfigProps.getBacklogSize() != null) {
                socketConfigBuilder.setBacklogSize(socketConfigProps.getBacklogSize());
            }
            customSocketConfig = socketConfigBuilder.build();

            httpClientBuilder.setDefaultSocketConfig(customSocketConfig);
        }

        if (sslContext == null) {
            sslContext = SSLContexts.createSystemDefault();
        }
        Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory> create()
                .register("http", new SocksProxyConnectionSocketFactory())
                .register("https", new SocksProxySSLConnectionSocketFactory(sslContext)).build();
        HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory = null;
        SchemePortResolver schemePortResolver = null;
        DnsResolver dnsResolver = null;
        long connTimeToLive = httpClientBuilderProperties.getConnTimeToLive() != null
                ? httpClientBuilderProperties.getConnTimeToLive()
                : -1;
        TimeUnit connTimeToLiveTimeUnit = TimeUnit.MILLISECONDS;
        PoolingHttpClientConnectionManager poolingmgr = new PoolingHttpClientConnectionManager(socketFactoryRegistry, connFactory,
                schemePortResolver, dnsResolver, connTimeToLive, connTimeToLiveTimeUnit);
        if (customSocketConfig != null) {
            poolingmgr.setDefaultSocketConfig(customSocketConfig);
        }
        if (httpClientBuilderProperties.getMaxConnTotal() != null) {
            poolingmgr.setMaxTotal(httpClientBuilderProperties.getMaxConnTotal());
        }
        if (httpClientBuilderProperties.getMaxConnPerRoute() != null) {
            poolingmgr.setDefaultMaxPerRoute(httpClientBuilderProperties.getMaxConnPerRoute());
        }

        httpClientBuilder.setConnectionManager(poolingmgr);

        CmpHttpClientRequestConfigProperties requestConfigProperties = httpClientBuilderProperties.getRequestConfig();
        if (requestConfigProperties != null) {
            httpClientBuilder.setDefaultRequestConfig(requestConfigProperties.build());
        }

        // proxy config
        if (proxy != null && Proxy.Type.HTTP.equals(proxy.type())) {
            InetSocketAddress address = (InetSocketAddress) proxy.address();
            httpClientBuilder.setProxy(new HttpHost(address.getHostString(), address.getPort()));
        }

        return httpClientBuilder;
    }

}
